﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Netfraction.Network.Protocol
{
    public class ProtocolMessage
    {
        public enum MessageTypeEnum
        {
            Unknown,    // this shouldn't happen, but treat it like an Info message - it's the safest one
            Broadcast,
            Client,
            Direct,
            Echo,
            Feature,
            Hub,
            Info,
            Udp
        }

        private MessageTypeEnum _messageType;
        private string _messageCode;
        private List<ProtocolParameter> _parameters;

        private string _sessionId;
        private string _clientId;
        private string _targetSessionId;

        public string TargetSessionId
        {
            get { return _targetSessionId; }
            set { _targetSessionId = value; }
        }

        public string SessionId
        {
            get { return _sessionId; }
            set { _sessionId = value; }
        }

        public ProtocolMessage()
	    {
            _messageType = MessageTypeEnum.Hub;
            _messageCode = "";
            _parameters = new List<ProtocolParameter>();
	    }

        public ProtocolMessage(MessageTypeEnum messageType, string messageCode)
            : this()
        {
            _messageType = messageType;
            _messageCode = messageCode;
        }

        public ProtocolMessage(MessageTypeEnum messageType, string messageCode, string sid)
            : this()
        {
            _messageType = messageType;
            _messageCode = messageCode;
            _sessionId = sid;
        }

        public ProtocolMessage(MessageTypeEnum messageType, string messageCode, string sid, string targetSid)
            : this()
        {
            _messageType = messageType;
            _messageCode = messageCode;
            _sessionId = sid;
            _targetSessionId = targetSid;
        }

        /// <summary>
        /// Decode a UTF-8 encoded message into a string
        /// </summary>
        public static string DecodeMessage(byte[] buffer, int offset, int length)
        {
            return Encoding.UTF8.GetString(buffer, offset, length);
        }

        /// <summary>
        /// Decode a UTF-8 encoded message into a string
        /// </summary>
        public static string DecodeMessage(byte[] buffer, int length)
        {
            return DecodeMessage(buffer, 0, length);
        }

        /// <summary>
        /// Encode a message into UTF-8
        /// </summary>
        public static byte[] EncodeMessage(string message)
        {
            // Prepare Message
            message.Trim();
            message += "\n";

            // Encode Message
            return System.Text.Encoding.UTF8.GetBytes(message);
        }

        public static List<ProtocolMessage> ProcessIncomingMessages(string messages)
        {
            List<ProtocolMessage> protocolMessageList = new List<ProtocolMessage>();

            ProtocolMessage proto;
            foreach (string msg in messages.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries))
            {
                if (!string.IsNullOrEmpty(msg))
                {
                    proto = new ProtocolMessage();
                    proto.Parse(msg);
                    protocolMessageList.Add(proto);
                }
            }

            return protocolMessageList;
        }

        public void Parse(string message)
        {
            message = message.Trim();

            if (!string.IsNullOrEmpty(message))
            {
                _messageType = GetMessageTypeFromChar(message[0]);
                _messageCode = message.Substring(1, 3);

                List<string> data = new List<string>(message.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));

                // location parameters begin
                int dataStart = 1;

                // for C, I and H messages, there is no SID or CID passed.
                // a B message begins with a SID
                if (_messageType == MessageTypeEnum.Broadcast ||
                    _messageType == MessageTypeEnum.Direct ||
                    _messageType == MessageTypeEnum.Echo ||
                    _messageType == MessageTypeEnum.Feature)
                {
                    _sessionId = data[dataStart];
                    dataStart++;
                }

                // a D and E message begin with a Local SID then a Target SID
                if (_messageType == MessageTypeEnum.Direct || _messageType == MessageTypeEnum.Echo)
                {
                    _targetSessionId = data[dataStart];
                    dataStart++;
                }

                // a U message begins with a CID
                if (_messageType == MessageTypeEnum.Udp)
                {
                    _clientId = data[dataStart];
                }

                if (data.Count >= dataStart)
                {
                    ProtocolParameter param = null;
                    for (int i = dataStart; i < data.Count; i++)
                    {
                        param = new ProtocolParameter(data[i]);
                        if (param != null && !string.IsNullOrEmpty(param.ParameterValue))   // can maybe remove the IsNullOrEmpty, and the RemoveEmptyEntries on line 126. See REF extension issue
                        {
                            _parameters.Add(param);
                        }
                    }
                }
            }
        }

        private static MessageTypeEnum GetMessageTypeFromChar(char messageType)
        {
            try
            {
                switch (messageType)
                {
                    case 'B':
                        return MessageTypeEnum.Broadcast;
                    case 'C':
                        return MessageTypeEnum.Client;
                    case 'D':
                        return MessageTypeEnum.Direct;
                    case 'E':
                        return MessageTypeEnum.Echo;
                    case 'F':
                        return MessageTypeEnum.Feature;
                    case 'H':
                        return MessageTypeEnum.Hub;
                    case 'I':
                        return MessageTypeEnum.Info;
                    case 'U':
                        return MessageTypeEnum.Udp;
                    default:
                        throw new ArgumentException("MessageType is invalid in GetMessageTypeFromChar");
                }
            }
            catch (ArgumentException)
            {
                return MessageTypeEnum.Info;
            }
        }

        private string GetProtocolMessageCharByType(MessageTypeEnum type)
        {
            switch (type)
            {
                case MessageTypeEnum.Unknown:
                    return "I";         // shouldn't happen!
                case MessageTypeEnum.Broadcast:
                    return "B";
                case MessageTypeEnum.Client:
                    return "C";
                case MessageTypeEnum.Direct:
                    return "D";
                case MessageTypeEnum.Echo:
                    return "E";
                case MessageTypeEnum.Feature:
                    return "F";
                case MessageTypeEnum.Hub:
                    return "H";
                case MessageTypeEnum.Info:
                    return "I";
                case MessageTypeEnum.Udp:
                    return "U";
                default:
                    throw new ArgumentException("Invalid protocol message character. Was not B, C, D, E, F, H, I, or U.");
            }
        }

        /// <summary>
        /// Overrides the ToString method
        /// </summary>
        public override string ToString()
        {
            string message = GetProtocolMessageCharByType(_messageType) + _messageCode;

            // Check message type
            if (_messageType == MessageTypeEnum.Broadcast || _messageType == MessageTypeEnum.Feature)
                message += String.Format(CultureInfo.InvariantCulture, " {0} ", _sessionId);
            else if (_messageType == MessageTypeEnum.Direct || _messageType == MessageTypeEnum.Echo)
                message += String.Format(CultureInfo.InvariantCulture, " {0} {1} ", _sessionId, _targetSessionId);
            else if (_messageType == MessageTypeEnum.Udp)
                message += String.Format(CultureInfo.InvariantCulture, " {0}", _clientId);
            else
                message += " ";

            // Loop all parameters
            for (int i = 0; i < _parameters.Count; i++)
            {
                // the ProtocolParameter's ToString override will escape the parameter for us
                message += _parameters[i].ToString() + " ";
            }

            // Trim and return message
            return message.Trim();
        }

        public List<ProtocolParameter> Parameters
        {
            get { return _parameters; }
            set { _parameters = value; }
        }

        public MessageTypeEnum MessageType
        {
            get { return _messageType; }
            set { _messageType = value; }
        }

        public string MessageCode
        {
            get { return _messageCode; }
            set { _messageCode = value; }
        }
    }
}
